﻿using System;
using System.IO;
using System.Linq;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Configuration;

namespace LibraryGuard.Guards
{
	/// <summary>
	/// Removes empty directories.
	/// </summary>
	public class EmptyDirectoriesGuard
		: IGuard
	{
		#region Logger
		private static NLog.Logger Logger = NLog.LogManager.GetLogger("EmptyDirsGuard");
		#endregion

		#region Fields
		private readonly IConfiguration BaseConfig = null;
		#endregion

		#region IGuard Members
		/// <inheritdoc />
		public bool OnlySpecificExtensions
		{
			get { return false; }
		}

		/// <inheritdoc />
		public IActionList ActionList { get; set; }

		/// <inheritdoc />
		public void Run()
		{
			this.BaseConfig.Directories.ForEach(d => this.ActionList.Add(new CheckDirectory(d))); //Comparison is not needed - first run
		}

		/// <inheritdoc />
		public void FileSystemEvent(FileSystemEventArgs args, string baseDirectory)
		{
			if (args.ChangeType == WatcherChangeTypes.Deleted)
			{
				var dir = new DirectoryInfo(Path.GetDirectoryName(args.FullPath));
				Logger.Trace("File in directory '{0}' deleted, guard", dir.FullName);
				this.ActionList.Add(new CheckDirectory(dir), Comparers.FSI);
			}
		}
		#endregion

		#region Constructor
		public EmptyDirectoriesGuard(IConfiguration config, IGuardConfiguration guard)
		{
			Validate.Debug(() => config, v => v.NotNull());

			this.BaseConfig = config;

			Logger.Info("Empty directories guard enabled");
		}
		#endregion

		#region Actions
		/// <summary>
		/// Checks directory if it is empty or not.
		/// </summary>
		private sealed class CheckDirectory
			: IActionInfo
		{
			#region Private fields
			private readonly DirectoryInfo Directory = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.Directory; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return false; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				this.Directory.Refresh();
				if (!this.Directory.Exists)
				{
					Logger.Debug("Directory '{0}' doesn't exist, ignoring", this.Directory.FullName);
					return true;
				}

				Logger.Trace("Searching in directory '{0}'", this.Directory.FullName);
				try
				{
					Array.ForEach(this.Directory.GetDirectories(), s => actionList.Add(new CheckDirectory(s), Comparers.FSI));

					if (this.Directory.GetFileSystemInfos().Count(fsi => !fsi.Attributes.HasFlag(FileAttributes.Hidden)) == 0)
						actionList.Add(new DeleteDirectory(this.Directory), Comparers.FSI);
				}
				catch (Exception ex)
				{
					Logger.WarnException("Cannot guard directory '{0}'".FormatWith(this.Directory.FullName), ex);
					return false;
				}
				return true;
			}
			#endregion

			#region Constructor
			public CheckDirectory(DirectoryInfo di)
			{
				this.Directory = di;
			}
			#endregion
		}

		/// <summary>
		/// Edge action - delete empty directory.
		/// </summary>
		private sealed class DeleteDirectory
			: IActionInfo
		{
			#region Private fields
			private readonly DirectoryInfo Directory = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.Directory; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return true; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				this.Directory.Refresh();
				if (!this.Directory.Exists)
				{
					Logger.Debug("Directory '{0}' was deleted, ignoring", this.Directory.FullName);
					return true;
				}

				try
				{
					this.Directory.Delete(true);
					Logger.Info("Directory '{0}' is empty, deleted", this.Directory.FullName);
				}
				catch (Exception ex)
				{
					Logger.WarnException("Cannot delete directory '{0}'".FormatWith(this.Directory.FullName), ex);
					return false;
				}
				return true;
			}
			#endregion

			#region Constructor
			public DeleteDirectory(DirectoryInfo di)
			{
				this.Directory = di;
			}
			#endregion

			#region ToString
			public override string ToString()
			{
				return "Delete directory '{0}'".FormatWith(this.Directory.FullName);
			}
			#endregion
		}
		#endregion
	}
}
